#include "helper.h"
#include "monitor.h"
#include "reg.h"
#include "memory.h"

extern uint32_t instr;
extern char assembly[80];
extern char trace_buf[128];

/* decode I-type instrucion with unsigned immediate */
static void decode_imm_type(uint32_t instr)
{

	op_src1->type = OP_TYPE_REG;// i型指令源寄存器，目的寄存器，立即数
	op_src1->reg = (instr & RS_MASK) >> (RT_SIZE + IMM_SIZE);
	//fetch the value use the reg index
	op_src1->val = reg_w(op_src1->reg);
	
	//determine the type of src2
	op_src2->type = OP_TYPE_IMM;
	//fetch imm use mask 
	op_src2->imm = instr & IMM_MASK;
	//same as imm segment before shift
	op_src2->val = op_src2->imm;


	//determine the type of dest
	op_dest->type = OP_TYPE_REG;
	//calculate the index of reg
	op_dest->reg = (instr & RT_MASK) >> (IMM_SIZE);
}


/*........................instr realization .........................*/

make_helper(lui)
{

	decode_imm_type(instr);   //decode the instr first
	reg_w(op_dest->reg) = (op_src2->val << 16);
	//print the corresponding log text
	sprintf(assembly, "lui   %s,   0x%04x", REG_NAME(op_dest->reg), op_src2->imm);
	
}

make_helper(ori)
{

	decode_imm_type(instr);   //decode the instr first
	reg_w(op_dest->reg) = op_src1->val | op_src2->val;
	sprintf(assembly, "ori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
	
}

make_helper(addi){
	decode_imm_type(instr);
	int imm = op_src2->val; 
	int sign_src1 = op_src1->val;
	imm = (imm<<16)>>16;
	int outcome = imm+sign_src1;
	int temp = sign_src1&0x80000000; 
	int temp2 = imm&0x80000000;
	if((temp==temp2)&&((outcome&0x80000000)!=temp)){
		//Exception
		if(cpu.cp0.status.EXL==0){
			cpu.cp0.cause.ExcCode = Ov;
			cpu.cp0.epc = cpu.pc;
			cpu.pc = Trap_addr - 4;
			cpu.cp0.status.EXL = 1;
		}
		reg_w(op_dest->reg)=outcome;
	}else {
		reg_w(op_dest->reg) = outcome;
	}
	sprintf(assembly, "addi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(addiu){
	decode_imm_type(instr);
	int imm = op_src2->val;
	imm = (imm<<16)>>16;
	reg_w(op_dest->reg) = imm+op_src1->val;
	sprintf(assembly, "addiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(slti){
	decode_imm_type(instr);
	int temp = op_src1->val ;
	int imm = (op_src2->val);
	imm = (imm<<16)>>16;
	reg_w(op_dest->reg) = temp > imm ? 0 : 1;
	sprintf(assembly, "slti   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(sltiu){
	decode_imm_type(instr);
	int imm = (op_src2->val);
	imm = (imm<<16)>>16;
	reg_w(op_dest->reg) = (op_src1->val > ((uint32_t)imm)) ? 0 : 1; 
	sprintf(assembly, "sltiu   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(andi){
	decode_imm_type(instr);
	int temp = 0x0000ffff&op_src2->val;
	reg_w(op_dest->reg)=op_src1->val&temp;
	sprintf(assembly, "andi   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}

make_helper(xori){
	decode_imm_type(instr);
	int temp = 0x0000ffff&op_src2->val;
	reg_w(op_dest->reg)=op_src1->val^temp;
	sprintf(assembly, "xori   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->imm);
}
// guorui 2024.10.19
make_helper(beq)
{
	decode_imm_type(instr);
	if (reg_w(op_dest->reg) == reg_w(op_src1->reg))
	{
		int temp;
		if (op_src2->val & 0x8000)
		{
			temp = (0xFFFF << 16) | op_src2->val;
		}
		else
		{
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "beq %s, %s, 0x%04x", REG_NAME(op_src1->reg), REG_NAME(op_dest->reg), op_src2->val);
}

// guorui 2024.10.19
make_helper(bne)
{
	decode_imm_type(instr);
	op_dest->val = reg_w(op_dest->reg);
	if (op_src1->val != op_dest->val)
	{
		sprintf(assembly, "%x, %x\n", op_src1->val, op_dest->val);
		int target_offset = op_src2->val;
		target_offset = (target_offset << 16) >> 14;
		uint32_t address = (int)cpu.pc + target_offset;
		cpu.pc = address;
	}
	sprintf(assembly, "bne   %s,   %s,   0x%04x", REG_NAME(op_dest->reg), REG_NAME(op_src1->reg), op_src2->val);
}

// guorui 2024.10.19
make_helper(bgez)
{
	decode_imm_type(instr);
	if ((int)op_src1->val >= 0)
	{
		int temp;
		if (op_src2->val & 0x8000)
		{
			temp = (0xFFFF << 16) | op_src2->val;
		}
		else
		{
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "bgez %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

// guorui 2024.10.19
make_helper(bgtz)
{
	decode_imm_type(instr);
	if ((int)op_src1->val > 0)
	{
		int temp;
		if (op_src2->val & 0x8000)
		{
			temp = (0xFFFF << 16) | op_src2->val;
		}
		else
		{
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "bgtz %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

// guorui 2024.10.19
make_helper(blez)
{
	decode_imm_type(instr);
	if ((int)op_src1->val <= 0)
	{
		int temp;
		if (op_src2->val & 0x8000)
		{
			temp = (0xFFFF << 16) | op_src2->val;
		}
		else
		{
			temp = op_src2->val;
		}
		uint32_t addr = (int)cpu.pc + (temp << 2);
		cpu.pc = addr;
	}
	sprintf(assembly, "blez %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

// guorui 2024.10.19
make_helper(bltz)
{
	decode_imm_type(instr);
	if ((int)op_src1->val < 0)
	{
		int temp;
		if (op_src2->val & 0x8000)
		{
			temp = (0xFFFF << 16) | op_src2->val;
		}
		else
		{
			temp = op_src2->val;
		}
		uint32_t addr;
		if (temp < 0)
		{
			uint32_t a = (uint32_t)(~temp + 1);
			addr = cpu.pc - (a << 2);
		}
		else
		{
			addr = cpu.pc + (temp << 2);
		}
		cpu.pc = addr;
	}
	sprintf(assembly, "bltz %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

// guorui 2024.10.19
make_helper(bgezal)
{
	decode_imm_type(instr);
	if (((int)op_src1->val) >= 0)
	{
		int temp;
		if (op_src2->val & 0x8000)
		{
			temp = (0xFFFF << 16) | op_src2->val;
		}
		else
		{
			temp = op_src2->val;
		}
		uint32_t addr = cpu.pc + (temp << 2);
		reg_w(31) = cpu.pc + 8;
		cpu.pc = addr;
	}
	else
	{
		reg_w(31) = cpu.pc + 8;
	}
	sprintf(assembly, "bgezal %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

// guorui 2024.10.19
make_helper(bltzal)
{
	decode_imm_type(instr);
	if (((int)op_src1->val) < 0)
	{
		int temp;
		if (op_src2->val & 0x8000)
		{
			temp = (0xFFFF << 16) | op_src2->val;
		}
		else
		{
			temp = op_src2->val;
		}
		uint32_t addr = cpu.pc + (temp << 2);
		reg_w(31) = cpu.pc + 8;
		cpu.pc = addr;
	}
	else
	{
		reg_w(31) = cpu.pc + 8;
	}
	sprintf(assembly, "bltzal %s, 0x%04x", REG_NAME(op_src1->reg), op_src2->val);
}

// lb
make_helper(lb)
{
	decode_imm_type(instr);
	// int imm = (((op_src2 -> val & 0x8000) >> 15) & 0x0001) ? ((0xFFFF << 16) | op_src2 -> val) : op_src2 -> val;
	int imm = ((op_src2 -> val) << 16) >> 16;
	uint32_t addr = imm + op_src1 -> val;
	int value = mem_read(addr, 1);
	value = (value << 24) >> 24;
	reg_w(op_dest -> reg) = value;
	sprintf(assembly, "lb   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg)); 
}

// lbu
make_helper(lbu)
{
	decode_imm_type(instr);
	int offset = ((op_src2 -> val) << 16) >> 16;
	uint32_t addr = offset + op_src1 -> val;
	uint32_t value = mem_read(addr, 1);
	reg_w(op_dest -> reg) = value;
	sprintf(assembly, "lbu   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg));
}

// lh
make_helper(lh)
{
	decode_imm_type(instr);
	int offset = ((op_src2 -> val) << 16) >> 16;
	uint32_t addr = offset + op_src1 -> val;

	if(addr & 0x1)
	{
		if(cpu.cp0.status.EXL == 0)
		{
			cpu.cp0.cause.ExcCode = AdEL;
			cpu.cp0.epc = cpu.pc;
			cpu.pc = Trap_addr - 4;
			cpu.cp0.status.EXL = 1;
		}
	}
	else
	{
		int value = mem_read(addr, 2);
		value = (value << 16) >> 16;
		reg_w(op_dest -> reg) = value;
	}

	sprintf(assembly, "lh   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg)); 
	
}

// lhu
make_helper(lhu)
{
	decode_imm_type(instr);
	int offset = ((op_src2 -> val) << 16) >> 16;
	uint32_t addr = offset + op_src1 -> val;

	if(addr & 0x1)
	{
		if(cpu.cp0.status.EXL == 0)
		{
			cpu.cp0.cause.ExcCode = AdEL;
			cpu.cp0.epc = cpu.pc;
			cpu.pc = Trap_addr - 4;
			cpu.cp0.status.EXL = 1;
		}
	}
	else
	{
		uint32_t value = mem_read(addr, 2);
		reg_w(op_dest -> reg) = value;
	}

	sprintf(assembly, "lhu   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg)); 
}

// lw
make_helper(lw)
{
	decode_imm_type(instr);
	int offset = ((op_src2 -> val) << 16) >> 16;
	uint32_t addr = offset + op_src1 -> val;

	if((addr & 0x3) != 0)
	{
		if(cpu.cp0.status.EXL == 0)
		{
			cpu.cp0.cause.ExcCode = AdEL;
			cpu.cp0.epc = cpu.pc;
			cpu.pc = Trap_addr - 4;
			cpu.cp0.status.EXL = 1;
		}
	}
	else
	{
		uint32_t value = mem_read(addr, 4);
		reg_w(op_dest -> reg) = value;
	}

	sprintf(assembly, "lw   %s,  0x%04x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg)); 
}

// sb
make_helper(sb)
{
	decode_imm_type(instr);
	int offset = ((op_src2 -> val) << 16) >> 16;
	uint32_t addr = offset + op_src1 -> val;

	mem_write(addr, 1, reg_w(op_dest -> reg));

	sprintf(assembly, "sb   %s,  0x%08x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg)); 
}

// sh
make_helper(sh)
{
	decode_imm_type(instr);
	int offset = ((op_src2 -> val) << 16) >> 16;
	uint32_t addr = offset + op_src1 -> val;

	if((addr & 0x1))
	{
		if(cpu.cp0.status.EXL == 0)
		{
			cpu.cp0.cause.ExcCode = AdEL;
			cpu.cp0.epc = cpu.pc;
			cpu.pc = Trap_addr - 4;
			cpu.cp0.status.EXL = 1;
		}
	}
	else
	{
		mem_write(addr, 2, reg_w(op_dest -> reg));
	}

	sprintf(assembly, "sh   %s,  0x%08x(%s)", REG_NAME(op_dest->reg),  op_src2->val, REG_NAME(op_src1->reg)); 
}

// sw
make_helper(sw){
	decode_imm_type(instr);
	int offset=op_src2->val;
	offset=(offset<<16)>>16;
	int base=op_src1->val;
	int addr=base+offset;
	if(addr&0x3)
	{
		if(cpu.cp0.status.EXL==0)
		{
			cpu.cp0.cause.ExcCode=AdES;
			cpu.cp0.epc=cpu.pc;
			cpu.pc=Trap_addr - 4;
			cpu.cp0.status.EXL=1;
		}
	}
	else
	{
			mem_write((uint32_t) addr, 4, reg_w(op_dest->reg));
	}
	sprintf(assembly, "sw %s, 0x%08x(%s)", REG_NAME(op_dest->reg), op_src2->val, REG_NAME(op_src1->reg));
}